package com.djbx.points.common.core.utils.lock.impl;

import com.djbx.points.common.core.utils.ResponseCode;
import com.djbx.points.common.core.utils.ServiceResponse;
import com.djbx.points.common.core.utils.lock.ILock;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;

import java.util.concurrent.Callable;
import java.util.concurrent.TimeUnit;

/**
 * 〈〉
 *
 * @author sader
 * @date 2021/11/26
 * @since 1.0.0
 */
@Slf4j
public class RedissionLock implements ILock {

    private final RLock lock;

    RedissionLock(Object lock, String key) {
        RedissonClient redissonClient = (RedissonClient) lock;
        this.lock = redissonClient.getLock(KEY_DISTRIBUTED_LOCK + key);
    }

    @Override
    public <T> ServiceResponse<T> tryLock(Callable<ServiceResponse<T>> callable) {
        return tryLock(callable, DEFAULT_LEASE_TIME);
    }

    @Override
    public <T> ServiceResponse<T> tryLock(long waitTime, Callable<ServiceResponse<T>> callable) {
        return tryLock(waitTime, callable, DEFAULT_LEASE_TIME);
    }

    @Override
    public <T> ServiceResponse<T> tryLock(Callable<ServiceResponse<T>> callable, long leaseTime) {
        return tryLock(0, callable, leaseTime);
    }

    @Override
    public <T> ServiceResponse<T> tryLock(long waitTime, Callable<ServiceResponse<T>> callable, long leaseTime) {
        ServiceResponse<T> resp = null;
        try {
            waitTime = waitTime <= 0 ? 0 : waitTime;
            leaseTime = leaseTime <= DEFAULT_LEASE_TIME ? DEFAULT_LEASE_TIME : leaseTime;
            boolean locked = lock.tryLock(waitTime, leaseTime, TimeUnit.MICROSECONDS);
            log.debug("[{}-{}]trylock[{}]: {}", Thread.currentThread().getId(), this.hashCode(), locked, lock.getName());
            if (locked) {
                resp = doTryCall(callable);
            } else {
                log.debug("[{}-{}]locked fail: {}", Thread.currentThread().getId(), this.hashCode(), lock.getName());
                resp = new ServiceResponse<>(ResponseCode.WAIT_OPERATION_ERROR);
            }

        } catch (InterruptedException e) {
            log.warn(e.getMessage());
            Thread.currentThread().interrupt();
            resp = new ServiceResponse<>(ResponseCode.REPEAT_OPERATION_ERROR);
        }
        return resp;
    }

    private <T> ServiceResponse<T> doTryCall(Callable<ServiceResponse<T>> callable) {
        ServiceResponse<T> resp;
        try {
            if (log.isDebugEnabled()) {
                if (lock.isLocked()) {
                    if (lock.isHeldByCurrentThread()) {
                        log.debug("[{}-{}]locked success by current thread", Thread.currentThread().getId(), this.hashCode());
                    } else {
                        log.debug("[{}-{}]locked success by other thread", Thread.currentThread().getId(), this.hashCode());
                    }
                } else {
                    log.debug("[{}-{}]does not locked", Thread.currentThread().getId(), this.hashCode());
                }
            }
            resp = callable.call();

        } catch (Exception e) {
            log.error("", e);
            resp = new ServiceResponse<>(ResponseCode.FAILED.getCode(), e.getMessage());
        } finally {
            unlock();
        }
        return resp;
    }


    @Override
    public <T> ServiceResponse<T> lock(Callable<ServiceResponse<T>> callable) {
        ServiceResponse<T> resp;
        //加锁（阻塞等待），默认过期时间是30秒
        lock.lock();
        log.debug("[{}-{}]locked: {}", Thread.currentThread().getId(), this.hashCode(), lock.getName());
        try {
            //如果业务执行过长，Redisson会自动给锁续期
            resp = callable.call();
        } catch (Exception e) {
            log.error("", e);
            resp = new ServiceResponse<>(ResponseCode.FAILED.getCode(), e.getMessage());
        } finally {
            //解锁，如果业务执行完成，就不会继续续期，即使没有手动释放锁，在30秒过后，也会释放锁
            unlock();
        }
        return resp;
    }

    private void unlock() {
        if (lock.isLocked()) {
            if (lock.isHeldByCurrentThread()) {
                lock.unlock();
                log.debug("[{}-{}]release lock: {}", Thread.currentThread().getId(), this.hashCode(), lock.getName());
            } else {
                log.debug("[{}-{}]cannot released, other node lock: {}", Thread.currentThread().getId(), this.hashCode(), lock.getName());
            }
        } else {
            log.debug("[{}-{}]cannot released, does not locked: {}", Thread.currentThread().getId(), this.hashCode(), lock.getName());
        }
    }
}
